//go:build !windows && !nacl && !plan9
// +build !windows,!nacl,!plan9

package v1

import (
	"context"
	"fmt"

	// "log/syslog"
	// "/Volumes/cf/dev/go/go13.4/src/log/syslog/syslog_unix.go"
	"time"

	"qabx.net/common/types/v1/syslog"

	log "github.com/sirupsen/logrus"
	"golang.org/x/sync/semaphore"
)

type SysLog struct {
	sysLog         *syslog.Writer
	status         int
	errorCount     int
	stop           chan struct{}
	wrong          int
	Proto          string
	URL            string
	Priority       syslog.Priority
	AppName        string
	close          int
	MaxParallelism int64
	locker         *semaphore.Weighted
	stopLocker     *semaphore.Weighted
	monitorSem     *semaphore.Weighted
	running        int
}

func (s *SysLog) newLog() error {
	s.stopLocker.Acquire(context.TODO(), 1)
	defer s.stopLocker.Release(1)
	sysLog, err := syslog.Dial(s.Proto, s.URL, s.Priority, s.AppName)
	if err != nil {
		return err
	}
	s.sysLog = sysLog
	s.errorCount = 0
	s.wrong = 0
	return nil
}

func (s *SysLog) Run() error {
	var err error
	if s.running == 1 {
		err = fmt.Errorf("Syslog alreadly running")
		return err
	}

	s.stop = make(chan struct{}, 1)
	sysLog, err := syslog.Dial(s.Proto, s.URL, s.Priority, s.AppName)
	if err != nil {
		return err
	}
	s.errorCount = 0
	s.wrong = 0
	s.sysLog = sysLog
	s.close = 0
	s.locker = semaphore.NewWeighted(s.MaxParallelism)
	s.stopLocker = semaphore.NewWeighted(1)
	s.monitorSem = semaphore.NewWeighted(1)
	go s.monitor()
	s.running = 1
	return nil
}

func (s *SysLog) Close() {
	s.stopLocker.Acquire(context.TODO(), 1)
	defer s.stopLocker.Release(1)
	s.wrong = 1
	s.stop <- struct{}{}
	for {
		if s.close == 1 {
			break
		}
		time.Sleep(time.Microsecond * 200)
	}
	s.sysLog.Close()
}

func (s *SysLog) monitor() {
	if !s.monitorSem.TryAcquire(1) {
		return
	}

	defer s.monitorSem.Release(1)
	for {
		select {
		case <-s.stop:
			goto exit
		default:
			if s.errorCount >= 10 {
				s.wrong = 1
				s.sysLog.Close()
				if err := s.newLog(); err != nil {
					time.Sleep(time.Second * 1)
					continue
				}
			}
			time.Sleep(time.Second * 1)
		}
	}
exit:
	s.close = 1
}

func (s *SysLog) SendLog(message string) error {
	s.locker.Acquire(context.TODO(), 1)
	defer s.locker.Release(1)
	for {
		if s.wrong == 0 {
			times := 0
			for {
				err := s.sysLog.Info(message)
				if err != nil {
					times++
				} else {
					return nil
				}
				if times == 3 {
					s.errorCount++
					log.Errorf("send syslog failed: %s", err.Error())
					return err
				}
			}
		}
		time.Sleep(time.Microsecond * 500)
	}
}
